package com.timing.feedback.config;

import com.timing.feedback.entity.UserPO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * JWT生成令牌、验证令牌、获取令牌
 * @author cwh
 */

@Component
@Slf4j
public class JwtTokenDao {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    /**
    * 私钥
    **/
    private static final String SECRET_KEY = "@!#DASJD$#FKAS__I#WOJOJ";

    /**
     * 业务名--redis中key前缀
     **/
    private static final String KEY_PRE = "authentication";

    /**
    * redis中过期时间 毫秒,
    **/
    private  static final long REDIS_EXPIRATION_TIME = 1000L*60*60*12;

    /**
    * 令牌有效期
    **/
    private  static final long EXPIRATION_TIME = 1000L*60*60*12;

    /**
     * 生成令牌
     *
     * @param userDetails 用户
     * @return 令牌
     */
    public String generateToken(UserPO userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put(Claims.SUBJECT, userDetails.getId());
        claims.put(Claims.ISSUED_AT, new Date().toString());
        return generateToken(claims);
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public Integer getUserIdFromToken(String token) {
        Integer id = null;
        try {
            Claims claims = getClaimsFromToken(token);
            log.info("claims:{}",claims.toString());
            id = Integer.valueOf(claims.getSubject());
        } catch (Exception e) {
            log.info("token解析错误:{}", e.getMessage());
        }
        return id;
    }

    /**
     * 判断令牌是否过期
     *
     * @param id 令牌
     * @return 是否过期
     */
    public boolean isTokenExpired(Integer id) {
        //从redis中判断id是否过期
        String key=KEY_PRE+":"+id;
        Long expire = stringRedisTemplate.getExpire(key);
        if(expire == null){
            return true;
        }
        return expire<=0;
    }

    /**
     * 更新redis中令牌时间
     * @param id id
     * @param token 令牌
     */
    public void refreshToken(Integer id, String token) {
        ValueOperations<String,String> valueOperations = stringRedisTemplate.opsForValue();
        String key=KEY_PRE+":"+id;
        valueOperations.set(key,token,REDIS_EXPIRATION_TIME, TimeUnit.MILLISECONDS);

    }

    /**
     * 验证令牌
     *
     * @param token 令牌
     * @return 是否有效
     */
    public boolean validateToken(String token){
        try {
            getClaimsFromToken(token);
        } catch (RuntimeException e) {
            return false;
        }
        return true;
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis()+ EXPIRATION_TIME);
        String token = Jwts.builder()
            .setClaims(claims)
            .setExpiration(expirationDate)
            .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
            .compact();
        //用户id作为key,token作为value存入redis
        ValueOperations<String,String> valueOperations = stringRedisTemplate.opsForValue();
        String key=KEY_PRE+":"+claims.get(Claims.SUBJECT);
        valueOperations.set(key,token,REDIS_EXPIRATION_TIME, TimeUnit.MILLISECONDS);
        return token;
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims getClaimsFromToken(String token) throws RuntimeException{
        Claims claims;
        claims = Jwts.parser()
            .setSigningKey(SECRET_KEY)
            .parseClaimsJws(token)
            .getBody();
        return claims;
    }

    /**
    * id是否存在
     * @param id
     * @param token
     * @return: boolean
    **/
    public boolean exist(Integer id, String token) {
        String key=KEY_PRE+":"+id;
        String redisToken = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.hasText(redisToken)){
           return redisToken.equals(token);
        }
        return false;
    }
}